#include "minix.h"

// xxx: only support V2
#define HOWMUCH	(BLOCKSIZ / (unsigned long))

#define get_zone(data, index) (((unsigned long *)data)[index])

static void minix_inode_free(struct inode * inoptr);
static BOOLEAN minix_can_delete(struct inode * inoptr);
static int minix_truncate(struct inode * inoptr, off_t length);
static off_t minix_size(struct inode * inoptr);
static void minix_setsize(struct inode * inoptr, off_t size);
static mode_t minix_mode(struct inode * inoptr);
static void minix_setmode(struct inode * inoptr, mode_t mode);
static dev_t minix_realdev(struct inode * inoptr);
static BOOLEAN minix_minode(
	struct inode * inoptr, off_t pos, unsigned long block
	);
static unsigned long minix_bmap(struct inode * inoptr, off_t pos);
static BOOLEAN minix_noitem(struct inode * inoptr);
static BOOLEAN minix_additem(
	struct inode * inoptr, const char * name, int * error, ino_t ino
	);
static BOOLEAN minix_delitem(
	struct inode * inoptr, const char * name, int * error
	);
static ino_t minix_findson(struct inode * inoptr, const char * name);
static BOOLEAN minix_real_filename(
	char * page, struct inode * inoptr, int * error
	);
static uid_t minix_getuid(struct inode * inoptr);
static void minix_setuid(struct inode * inoptr, uid_t uid);
static gid_t minix_getgid(struct inode * inoptr);
static void minix_setgid(struct inode * inoptr, gid_t gid);
static void minix_mknod(struct inode * inoptr, dev_t dev);
static int minix_blocksize(struct inode * inoptr);
static off_t minix_max_filesize(struct inode * inoptr);
static void minix_update(
	struct inode * inoptr, time_t atime, time_t mtime, time_t ctime
	);
static nlink_t minix_change_nlinks(struct inode * inoptr, int change);
static int minix_get_name_of_son(
	struct inode * fino, ino_t inum, char * name, int size
	);
static int minix_stat(struct inode * inoptr, struct stat * statbuf);
static int minix_readdir(int fd, struct dirent * dirp);

struct inode_operation minix_inode_operation = {
	&minix_inode_free,
	&minix_can_delete,
	&minix_truncate,
	&minix_size,
	&minix_setsize,
	&minix_mode,
	&minix_setmode,
	&minix_realdev,
	&minix_real_filename,
	&minix_minode,
	&minix_noitem,
	&minix_additem,
	&minix_delitem,
	&minix_findson,
	&minix_getuid,
	&minix_setuid,
	&minix_getgid,
	&minix_setgid,
	&minix_mknod,
	&minix_bmap,
	&minix_blocksize,
	&minix_max_filesize,
	&minix_update,
	&minix_change_nlinks,
	&minix_get_name_of_son,
	&minix_stat,
	&minix_readdir,
};

static void minix_inode_free(struct inode * inoptr)
{
	kfree(inoptr->i_data);
	inoptr->i_data = NULL;
}

static BOOLEAN minix_can_delete(struct inode * inoptr)
{
	struct minix_inode * minoptr = (struct minix_inode *)inoptr->i_data;

	if(!minoptr->i_nlinks)
		return TRUE;

	return FALSE;
}

/* assume length <= i_size */
static int dec_datazone_inside_block(struct inode *inoptr, off_t length)
{
	off_t block_offset;
	unsigned long block_index;
	struct buf_head *bh;
	struct buf_head *bh1;
	struct minix_super_block *msb;
	unsigned long *pblock;
	unsigned long *pblock1;	// for double indirect block.
	int inside_block;
	int ndzones;
	int nindirs;
	int scale;
	off_t size;
	int idx;
	int idx1;		// for double indirect block.
	struct minix_inode *minoptr = (struct minix_inode *)inoptr->i_data;

	ndzones = minoptr->i_ndzones;
	nindirs = minoptr->i_nindirs;
	msb = (struct minix_super_block *)inoptr->i_sb->s_data;
	scale = msb->s_lzsize;
	
	block_offset = minoptr->i_size & (BLOCKSIZ - 1);
	if (block_offset == 0)
	{
		return 1;
	}

	if (length > minoptr->i_size - block_offset)
	{
		inside_block = TRUE;
		goto out;
	}
	else
	{
		inside_block = FALSE;
		length = minoptr->i_size - block_offset;
	}

	size = minoptr->i_size;
	
	if (minoptr->i_size < (ndzones * (BLOCKSIZ << scale)))
	{
		idx = minoptr->i_size / (BLOCKSIZ << scale);
		block_index = minoptr->i_zone[idx];
		if (block_index != NOBLOCK)
		{
			minix_bitfree(inoptr->i_sb, block_index, ZMAP);
		}
		
		minoptr->i_zone[idx] = NOBLOCK;
		goto out;
	}

	if (minoptr->i_size < ((ndzones * (BLOCKSIZ << scale) +
				nindirs * (BLOCKSIZ << scale))))
	{
		// indirect block.
		size -= ndzones * (BLOCKSIZ << scale);
		idx = size / (BLOCKSIZ << scale);
		
		block_index = minoptr->i_zone[ndzones];
		if (block_index != NOBLOCK)
		{
			bh = bread(inoptr->i_dev, block_index);
			if (!bh)
			{
				return -EIO;
			}

			pblock = (unsigned long *)&bh->b_data[0];
			if (pblock[idx] != NOBLOCK)
			{
				minix_bitfree(inoptr->i_sb, pblock[idx], ZMAP);
				pblock[idx] = NOBLOCK;
				bh->b_flags |= B_DIRTY | B_DONE;
			}
			
			brelse(bh);
		
			if (idx == 0)
			{
				minix_bitfree(inoptr->i_sb, block_index, ZMAP);
				minoptr->i_zone[ndzones] = NOBLOCK;
			}
		}
		goto out;
	}

	// double indirect block.
	size -= ndzones * (BLOCKSIZ << scale);
	size -= nindirs * (BLOCKSIZ << scale);
	block_index = minoptr->i_zone[ndzones + 1];
	if (block_index == NOBLOCK)
	{
		goto out;
	}
	
	idx = size / (nindirs * (BLOCKSIZ << scale));

	bh = bread(inoptr->i_dev, block_index);
	if (!bh)
	{
		return -EIO;
	}

	pblock = (unsigned long *)&bh->b_data[0];
	if (pblock[idx] == NOBLOCK)
	{
		brelse(bh);

		goto out;
	}

	if (!(bh1 = bread(inoptr->i_dev, pblock[idx])))
	{
		brelse(bh);
		return -EIO;
	}

	size -= idx * (nindirs * (BLOCKSIZ << scale));
	idx1 = size / (BLOCKSIZ << scale);

	pblock1 = (unsigned long *)&bh1->b_data[0];
	if (pblock1[idx1] != NOBLOCK)
	{
		minix_bitfree(inoptr->i_sb, pblock1[idx1], ZMAP);
		pblock1[idx1] = NOBLOCK;
		bh1->b_flags |= B_DIRTY | B_DONE;
	}
	
	brelse(bh1);
		
	if (idx1 == 0)
	{
		minix_bitfree(inoptr->i_sb, pblock[idx], ZMAP);
		pblock[idx] = NOBLOCK;
		bh->b_flags |= B_DIRTY | B_DONE;

		if (idx == 0)
		{
			minix_bitfree(inoptr->i_sb, minoptr->i_zone[ndzones + 1], ZMAP);
			minoptr->i_zone[ndzones + 1] = NOBLOCK;
		}	
	}

	brelse(bh);
	
out:
	minoptr->i_size = length;
	inoptr->i_update |= MTIME;
	inoptr->i_dirty = 1;

	if (inside_block)
	{
		return 0;
	}
	else
	{
		return 1;
	}
}

static int free_inblock(struct inode *inoptr,
			struct buf_head *bh,
			int start_index,
			int end_index)
{
	int i;
	unsigned long *pblock;
	int scale;
	struct minix_inode *minoptr = (struct minix_inode *)inoptr->i_data;

	scale = minoptr->i_msb->s_lzsize;

	pblock = (unsigned long *)&bh->b_data[0];
	for (i = end_index; i >= start_index; i--)
	{
		minoptr->i_size -= (BLOCKSIZ << scale);
		
		if (pblock[i] == NOBLOCK)
		{
			continue;
		}

		minix_bitfree(inoptr->i_sb, pblock[i], ZMAP);
		pblock[i] = NOBLOCK;
		bh->b_flags |= B_DIRTY | B_DONE;
	}

	return 1;
}

static int free_dinblock(struct inode *inoptr,
			struct buf_head *bh,
			int start_index,
			int end_index)
{
	int i;
	unsigned long *pblock;
	struct buf_head *bh1;
	int error = 0;
	int nindirs;
	struct minix_inode *minoptr = (struct minix_inode *)inoptr->i_data;

	nindirs = minoptr->i_nindirs;
	pblock = (unsigned long *)&bh->b_data[0];

	for (i = end_index; i >= start_index; i--)
	{
		if (pblock[i] == NOBLOCK)
		{
			minoptr->i_size -= nindirs * (BLOCKSIZ << minoptr->i_msb->s_lzsize);
			continue;
		}

		bh1 = bread(inoptr->i_dev, pblock[i]);
		if (!bh1)
		{
			error = -EIO;
			goto out;
		}

		free_inblock(inoptr, bh1, 0, nindirs - 1);
		brelse(bh1);

		minix_bitfree(inoptr->i_sb, pblock[i], ZMAP);
		pblock[i] = NOBLOCK;
		bh->b_flags |= B_DIRTY | B_DONE;
	}

out:
	return error;
}

/* assume i_size & (BLOCKSIZ - 1) == 0 */
static int free_dinblock_offset(struct inode *inoptr,
				off_t offset)
{
	struct buf_head *bh;
	off_t size;
	int ndzones;
	unsigned long block_index;
	int error = 0;
	int start_index;
	int end_index;
	int scale;
	int nindirs;
	int tmp;
	struct minix_inode *minoptr = (struct minix_inode *)inoptr->i_data;

	scale = minoptr->i_msb->s_lzsize;	
	nindirs = minoptr->i_nindirs;
	ndzones = minoptr->i_ndzones;

	if ((block_index = minoptr->i_zone[ndzones + 1]) == NOBLOCK)
	{
		//DIE("Can not happend!\n");
		minoptr->i_size = offset;
		
		return 0;
	}

	bh = bread(inoptr->i_dev, block_index);
	if (!bh)
	{
		error = -EIO;
		return error;
	}

	size = minoptr->i_size;
	tmp = size - (ndzones * (BLOCKSIZ << scale)) -
			nindirs * (BLOCKSIZ << scale);
	end_index = tmp / (nindirs * (BLOCKSIZ << scale));

	if ((tmp %= (nindirs * (BLOCKSIZ << scale))))
	{
		int start;
		int end;
		struct buf_head *bh1;
		unsigned long *pblock;
		
		start = 0;
		end = tmp / (BLOCKSIZ << scale);
		end--;

		pblock = (unsigned long *)&bh->b_data[0];
		if (pblock[end_index] != NOBLOCK)
		{
			bh1 = bread(inoptr->i_dev, pblock[end_index]);
			if (!bh1)
			{
				error = -EIO;
				goto out;
			}

			free_inblock(inoptr, bh1, start, end);
			brelse(bh1);

			minix_bitfree(inoptr->i_sb, pblock[end_index], ZMAP);
			pblock[end_index] = NOBLOCK;
			bh->b_flags |= B_DIRTY | B_DONE;
		}
		else
		{
			minoptr->i_size -= (end + 1) * (BLOCKSIZ << scale);
		}
	}

	end_index--;
	
	tmp = offset - (ndzones * (BLOCKSIZ << scale)) -
			nindirs * (BLOCKSIZ << scale);

	start_index = tmp / (nindirs * (BLOCKSIZ << scale));
	if ((tmp %= (nindirs * (BLOCKSIZ << scale))))
	{
		start_index++;
	}

	if ((error = free_dinblock(inoptr, bh, start_index, end_index)) < 0)
	{
		goto out;
	}

	if (tmp)
	{
		unsigned long *pblock;
		struct buf_head *bh1;
		int index = start_index - 1;

		pblock = (unsigned long *)&bh->b_data[0];
		if ((block_index = pblock[index]) == NOBLOCK)
		{
			minoptr->i_size = offset;
			goto out;
		}

		bh1 = bread(inoptr->i_dev, block_index);
		if (!bh1) 
		{
			error = -EIO;
			goto out;
		}

		start_index = tmp;
		start_index /= (BLOCKSIZ << scale);
		if ((tmp %= (BLOCKSIZ << scale)))
		{
			start_index++;
		}

		end_index = nindirs - 1;
		free_inblock(inoptr, bh1, start_index, end_index);

		if (tmp)
		{
			minoptr->i_size -= BLOCKSIZ - tmp;
		}

		brelse(bh1);
	}

	if (start_index == 0)
	{
		minix_bitfree(inoptr->i_sb, minoptr->i_zone[ndzones + 1], ZMAP);
		minoptr->i_zone[ndzones + 1] = NOBLOCK;

		bh->b_flags |= B_DIRTY | B_DONE;
	}
out:
	brelse(bh);

	return error;
}

static int free_inblock_offset(struct inode *inoptr, off_t offset)
{
	struct buf_head *bh;
	off_t size;
	int ndzones;
	unsigned long block_index;
	int error = 0;
	int start_index;
	int end_index;
	int scale;
	int tmp;
	struct minix_inode *minoptr = (struct minix_inode *)inoptr->i_data;

	scale = minoptr->i_msb->s_lzsize;	
	ndzones = minoptr->i_ndzones;

	if ((block_index = minoptr->i_zone[ndzones]) == NOBLOCK)
	{
		minoptr->i_size = offset;
		return 0;
	}

	bh = bread(inoptr->i_dev, block_index);
	if (!bh)
	{
		error = -EIO;
		return error;
	}

	//printk("ndzones = %d, offset = %d", ndzones, offset);
	
	size = minoptr->i_size;
	tmp = size - (ndzones * (BLOCKSIZ << scale));
	end_index = tmp / (BLOCKSIZ << scale);

	if ((tmp %= (BLOCKSIZ << scale)))
	{
		unsigned long *pblock;

		pblock = (unsigned long *)&bh->b_data[0];
		if (pblock[end_index] != NOBLOCK)
		{
			minix_bitfree(inoptr->i_sb, pblock[end_index], ZMAP);
			pblock[end_index] = NOBLOCK;
			bh->b_flags |= B_DIRTY | B_DONE;
		}
		
		minoptr->i_size -= ((BLOCKSIZ << scale) - tmp);
	}
	
	end_index--;
	
	tmp = offset - (ndzones * (BLOCKSIZ << scale));
	start_index = tmp / (BLOCKSIZ << scale);
	if ((tmp %= (BLOCKSIZ << scale)))
	{
		start_index++;
	}

	//printk("start_index = %d, end_index = %d\n", start_index, end_index);
	
	if ((error = free_inblock(inoptr, bh, start_index, end_index)) < 0)
	{
		brelse(bh);
		return error;
	}

	if (tmp)
	{		
		minoptr->i_size -= BLOCKSIZ - tmp;
	}

	brelse(bh);

	if (start_index == 0)
	{
		block_index = minoptr->i_zone[ndzones];
		minoptr->i_zone[ndzones] = NOBLOCK;
		minix_bitfree(inoptr->i_sb, block_index, ZMAP);
	}

	return error;
}

static int dec_data_zone(struct inode *inoptr, off_t length)
{
	int i;
	int tmp;
	int scale;
	int ndzones;
	int nindirs;
	int error = 0;
	int start_index;
	int end_index;
	
	off_t size;
	off_t len;

	struct minix_super_block *msb;
	unsigned long block_index;
	struct minix_inode *minoptr = (struct minix_inode *)inoptr->i_data;

	if ((error = dec_datazone_inside_block(inoptr, length)) <= 0)
	{
		goto out;
	}

	//
	ndzones = minoptr->i_ndzones;
	nindirs = minoptr->i_nindirs;
	msb = (struct minix_super_block *)inoptr->i_sb->s_data;
	scale = msb->s_lzsize;
	size = minoptr->i_size;

	// double indirect 
	if (minoptr->i_size > ((ndzones * (BLOCKSIZ << scale) +
			nindirs * (BLOCKSIZ << scale))))
	{
		len = length;
		if (len < ((ndzones * (BLOCKSIZ << scale) +
			nindirs * (BLOCKSIZ << scale))))
		{
			len = ((ndzones * (BLOCKSIZ << scale) +
				nindirs * (BLOCKSIZ << scale)));
		}

		if ((error = free_dinblock_offset(inoptr, len)) < 0)
		{
			goto out;
		}

		if (length == minoptr->i_size)
		{
			goto out;
		}
	}

	if (minoptr->i_size > (ndzones * (BLOCKSIZ << scale)))
	{
		len = length;

		if (len < (ndzones * (BLOCKSIZ << scale)))
		{
			len = (ndzones * (BLOCKSIZ << scale));
		}

		//printk("len = %d\n", len);
		
		if ((error = free_inblock_offset(inoptr, len)) < 0)
		{
			goto out;
		}

		if (length == minoptr->i_size)
		{
			goto out;
		}
	}

	end_index = minoptr->i_size / (BLOCKSIZ << scale);
	tmp = minoptr->i_size % (BLOCKSIZ << scale);
	if (tmp)
	{
		block_index = minoptr->i_zone[end_index];
		if (block_index != NOBLOCK)
		{
			minix_bitfree(inoptr->i_sb, block_index, ZMAP);
			minoptr->i_zone[end_index] = NOBLOCK;
		}

		minoptr->i_size -= tmp;
	}
	
	end_index--;
	
	start_index = length / (BLOCKSIZ << scale);
	tmp = length % (BLOCKSIZ << scale);
	if (tmp)
	{
		start_index++;
	}
	
	for (i = end_index; i >= start_index; i--)
	{
		block_index = minoptr->i_zone[i];
		if (block_index != NOBLOCK)
		{
			minix_bitfree(inoptr->i_sb, block_index, ZMAP);
			minoptr->i_zone[i] = NOBLOCK;
		}
		
		minoptr->i_size -= (BLOCKSIZ << scale);
	}

	if (tmp)
	{
		minoptr->i_size -= (BLOCKSIZ << scale) - tmp;
	}

	if (length != minoptr->i_size)
	{
		DIE("Can not happen!\n");
	}

out:
	inoptr->i_update |= MTIME;
	inoptr->i_dirty = 1;
	return error;
}

static int minix_truncate(struct inode *inoptr, off_t length)
{
	int ret = 0;
	struct minix_inode *minoptr = (struct minix_inode *)inoptr->i_data;
	
	if (length > FILESIZE_MAX)
	{
		return -E2BIG;
	}
	
	if (length < 0)
	{
		return -EINVAL;
	}

	if (inoptr->i_dev == NODEV)
	{
		return -ENODEV;
	}

	//printk("i_size = %d, length = %d\n", inoptr->i_size, length);

	if (length > minoptr->i_size)
	{
		minoptr->i_size = length;
		inoptr->i_update |= MTIME;
		inoptr->i_dirty = 1;

		return 0;
	}
	else if (length < minoptr->i_size)
	{
		ret = dec_data_zone(inoptr, length);
	}

	return ret;
}

static off_t minix_size(struct inode * inoptr)
{
	struct minix_inode * minoptr = (struct minix_inode *)inoptr->i_data;

	return minoptr->i_size;
}

static void minix_setsize(struct inode * inoptr, off_t size)
{
	struct minix_inode * minoptr = (struct minix_inode *)inoptr->i_data;

	minoptr->i_size = size;
}

static mode_t minix_mode(struct inode * inoptr)
{
	struct minix_inode * minoptr = (struct minix_inode *)inoptr->i_data;

	return minoptr->i_mode;
}

static void minix_setmode(struct inode * inoptr, mode_t mode)
{
	struct minix_inode * minoptr = (struct minix_inode *)inoptr->i_data;

	minoptr->i_mode = mode;
}

static dev_t minix_realdev(struct inode * inoptr)
{
	struct minix_inode * minoptr = (struct minix_inode *)inoptr->i_data;

	return minoptr->i_zone[0];
}

static unsigned long minix_bmap(struct inode * inoptr, off_t pos)
{
	unsigned long block_pos, block;
	unsigned long zone_pos, zone, excess;
	int scale, block_off, dzones, nindirs, index;
	struct buf_head * bh;
	struct minix_inode * minoptr;
	struct minix_super_block * msb;

	minoptr = (struct minix_inode *)inoptr->i_data;
	msb = minoptr->i_msb;

	scale = msb->s_lzsize;
	block_pos = pos / BLOCKSIZ;
	zone_pos = block_pos >> scale;
	block_off = block_pos - (zone_pos << scale);

	dzones = minoptr->i_ndzones;
	nindirs = minoptr->i_nindirs;

	if(zone_pos < dzones){
		 zone = minoptr->i_zone[zone_pos];
		 if(zone == NOZONE)
			 return NOBLK; // not NOBLOCK

		 block = (zone << scale) + block_off;

		 return block;
	}

	excess = zone_pos - dzones;

	/* single indirect */
	if(excess < nindirs)
		zone = minoptr->i_zone[dzones];
	else{
		/* double indirect */
		zone = minoptr->i_zone[dzones + 1];
		if(zone == NOZONE)
			return NOBLK;

		/* double indirect offset */
		excess -= nindirs;

		block = zone << scale;
		bh = bread(inoptr->i_dev, block);
		if(!bh){
			printk("error when reading block\n");
			return NOBLK;
		}

		index = (excess / nindirs);
		zone = get_zone(bh->b_data, index);

		brelse(bh);

		excess %= nindirs;
	}

	if(zone == NOZONE)
		return NOBLK;

	block = zone << scale;

	bh = bread(inoptr->i_dev, block);
	if(!bh){
		printk("bread error\n");
		return NOBLK;
	}

	zone = get_zone(bh->b_data, excess);

	brelse(bh);

	if(zone == NOZONE)
		return NOBLK;

	block = (zone << scale) + block_off;

	return block;
}

static BOOLEAN minix_minode(struct inode * inoptr, off_t pos, unsigned long blk)
{
	unsigned long zone, block;
	unsigned long zone_pos, tmpzone, excess;
	int scale, dzones, nindirs, index;
	struct buf_head * bh;
	struct minix_inode * minoptr;

	minoptr = (struct minix_inode *)inoptr->i_data;

	scale = minoptr->i_msb->s_lzsize;
	zone = blk >> scale;
	zone_pos = (pos / BLOCKSIZ) >> scale;

	/* direct zones */
	dzones = minoptr->i_ndzones;

	if(zone_pos < dzones){
		minoptr->i_zone[zone_pos] = zone;
		inoptr->i_update |= MTIME;
		inoptr->i_dirty = 1;

		return TRUE;
	}

	/* indirect zones */
	nindirs = minoptr->i_nindirs;

	excess = zone_pos - dzones;
	if(excess < nindirs){
		/* single indirect */
		tmpzone = minoptr->i_zone[dzones];
		if(tmpzone == NOZONE){
			/*
			 * file size is just lager than single indirect
			 * excess may be not zero if lseek to after file size
			 */
			//if(excess != 0)
			//	DIE("BUG: excess must be zero\n");

			tmpzone = minix_bitalloc(inoptr->i_sb, ZMAP);
			if(tmpzone == NOZONE){
				printk("out of disk space\n");
				return FALSE;
			}

			minoptr->i_zone[dzones] = tmpzone;

			block = tmpzone << scale;

			// allocate a block and clear data
			bh = minix_getblk(inoptr->i_dev, block);

			// getblk won't fail
			memset(bh->b_data, 0, BLOCKSIZ);
		}else{
			block = tmpzone << scale;

			// xxx: is zone size the same with block size?
			bh = bread(inoptr->i_dev, block);
			if(!bh){
				printk("error when reading block\n");
				return FALSE;
			}
		}
	
		get_zone(bh->b_data, excess) = zone;
		bh->b_flags |= B_DIRTY | B_DONE;
		brelse(bh);

		inoptr->i_update |= MTIME;
		inoptr->i_dirty = 1;

		return TRUE;
	}

	// double indirect offset
	excess -= nindirs;
	index = excess / nindirs;

	// double indirect
	tmpzone = minoptr->i_zone[dzones + 1];
	if(tmpzone == NOZONE){
		tmpzone = minix_bitalloc(inoptr->i_sb, ZMAP);
		if(tmpzone == NOZONE){
			printk("out of disk space\n");
			return FALSE;
		}

		minoptr->i_zone[dzones + 1] = tmpzone;
		inoptr->i_update |= MTIME;
		inoptr->i_dirty = 1;

		/*
		 * file size is just lager than signle indirect
		 * excess may be not zero if lseek to after file size
		 */
		//if(index != 0)
		//	DIE("BUG: index must be zero\n");

		block = tmpzone << scale;

		// allocate a block and clear data
		bh = minix_getblk(inoptr->i_dev, block);

		// getblk won't fail
		memset(bh->b_data, 0, BLOCKSIZ);

		// because won't be freed, diffirent from write
		bh->b_flags |= B_DIRTY | B_DONE;
	}else{
		block = tmpzone << scale;

		// xxx: is zone size the same with block size?
		bh = bread(inoptr->i_dev, block);
		if(!bh){
			printk("error when reading block\n");
			return FALSE;
		}
	}

	tmpzone = get_zone(bh->b_data, index);
	if(tmpzone == NOZONE){
		tmpzone = minix_bitalloc(inoptr->i_sb, ZMAP);
		if(tmpzone == NOZONE){
			printk("out of disk space\n");

			inoptr->i_update |= MTIME;
			inoptr->i_dirty = 1;
			brelse(bh);

			return FALSE;
		}

		get_zone(bh->b_data, index) = tmpzone;
		bh->b_flags |= B_DIRTY | B_DONE;
		brelse(bh);

		block = tmpzone << scale;

		/* allocate a block and clear data */	
		bh = minix_getblk(inoptr->i_dev, block);

		/* getblk won't fail */
		memset(bh->b_data, 0, BLOCKSIZ);
	}else{
		brelse(bh);

		block = tmpzone << scale;

		// xxx: is zone size the same with block size?
		bh = bread(inoptr->i_dev, block);
		if(!bh){
			printk("error when reading block\n");
			return FALSE;
		}
	}

	excess %= nindirs;
	if(get_zone(bh->b_data, excess) != NOZONE)
		DIE("cannot happen\n");

	get_zone(bh->b_data, excess) = zone;

	bh->b_flags |= B_DIRTY | B_DONE;
	brelse(bh);

	inoptr->i_update |= MTIME;
	inoptr->i_dirty = 1;

	return TRUE;
}

static BOOLEAN minix_noitem(struct inode * inoptr)
{
	int i;
	off_t pos;
	unsigned long block;
	struct buf_head * bh;
	struct direct * dirptr;
	struct minix_inode * minoptr;

	minoptr = (struct minix_inode *)inoptr->i_data;

	if(!S_ISDIR(minoptr->i_mode))
		DIE("BUG: inoptr must be a directory\n");

	for(pos = 0; pos < minoptr->i_size; pos += BLOCKSIZ){
		block = minix_bmap(inoptr, pos);
		if(block == NOBLK){
			printk("can't get NOBLOCK\n");
			return FALSE;
		}

		bh = bread(inoptr->i_dev, block);
		if(!bh){
			printk("error when reading block\n");
			return FALSE;
		}
	
		dirptr = (struct direct *)bh->b_data;
		for(i = 0; i < (BLOCKSIZ / DIRECT_SIZE); i++, dirptr++)
			if(dirptr->d_ino 
				&& strncmp(".", dirptr->d_name, 30)
				&& strncmp("..", dirptr->d_name, 30)){
				brelse(bh);
				return FALSE;
		}

		brelse(bh);
	}

	return TRUE;
}

static BOOLEAN minix_additem(
	struct inode * inoptr, const char * name, int * error, ino_t ino
	)
{
	off_t pos;
	int i, which;
	unsigned long block;
	struct direct * dirptr;
	struct buf_head * bh;
	struct minix_inode * minoptr;

	minoptr = (struct minix_inode *)inoptr->i_data;

	if(!S_ISDIR(minoptr->i_mode))
		DIE("BUG: cannot happen\n");

	// this flag must be checked outside
	if(inoptr->i_sb->s_roflag){
		*error = -EROFS;
		return FALSE;
	}

	if(strlen(name) > 30){
		*error = -ENAMETOOLONG;
		return FALSE;
	}

	which = 0;
	bh = NULL;
	dirptr = NULL;

	for(pos = 0; pos < minoptr->i_size; pos += BLOCKSIZ){
		block = minix_bmap(inoptr, pos);
		if(block == NOBLK){
			PRINTK("bmap error\n");
			*error = -EIO;
			return FALSE;
		}

		bh = bread(inoptr->i_dev, block);
		if(!bh){
			PRINTK("bread error\n");
			*error = -EIO;
			return FALSE;
		}
	
		dirptr = (struct direct *)bh->b_data;
		for(i = 0; i < (BLOCKSIZ / DIRECT_SIZE); i++, dirptr++){
			if(dirptr->d_ino != 0)
				continue;

			if(pos + (i + 1) * DIRECT_SIZE > minoptr->i_size){
				if(pos + i * DIRECT_SIZE != minoptr->i_size){
					brelse(bh);
					PRINTK("add item error\n");
					*error = -EIO;
					return FALSE;
				}
			}

			which = i;

			goto nowdoit;
		}

		brelse(bh);
	}

	if(minoptr->i_size >= FILESIZE_MAX){
		*error = -EFBIG;
		return FALSE;
	}

	block = minix_bitalloc(inoptr->i_sb, ZMAP);
	if(block == NOZONE){
		PRINTK("out of disk space\n");
		*error = -ENOSPC;
		return FALSE;
	}

	bh = minix_getblk(inoptr->i_dev, block);

	memset(bh->b_data, 0, BLOCKSIZ);
	dirptr = (struct direct *)bh->b_data;

	if(!minix_minode(inoptr, pos, block >> minoptr->i_msb->s_lzsize)){
		brelse(bh);
		minix_bitfree(inoptr->i_sb, block, ZMAP);
		*error = -ENOSPC;
		return FALSE;
	}

nowdoit:
	strncpy(dirptr->d_name, name, 30);
	dirptr->d_ino = ino;

#if 0
	bh->b_flags |= B_ASY;
	bwrite(bh);
#else
	bh->b_flags |= B_DIRTY | B_DONE;
	brelse(bh);
#endif

	if(pos + (which + 1) * DIRECT_SIZE > minoptr->i_size)
		minoptr->i_size += DIRECT_SIZE;

	inoptr->i_update |= MTIME;
	inoptr->i_dirty = 1;

	return TRUE;
}

/*
 * inoptr is a locked inode
 */
static BOOLEAN minix_delitem(
	struct inode * inoptr, const char * name, int * error
	)
{
	int i;
	off_t pos;
	unsigned long block;
	struct buf_head * bh;
	struct direct * dirptr;
	struct minix_inode * minoptr;

	minoptr = (struct minix_inode *)inoptr->i_data;

	if(!S_ISDIR(minoptr->i_mode))
		DIE("BUG: inoptr must be a directory\n");

	if(inoptr->i_sb->s_roflag){
		*error = -EROFS;
		return FALSE;
	}

	if(strlen(name) > 30){
		*error = -ENAMETOOLONG;
		return FALSE;
	}

	if(!strncmp(name, ".", 30)
		|| !strncmp(name, "..", 30)){
		*error = -EPERM;
		return FALSE;
	}

	for(pos = 0; pos < minoptr->i_size; pos += BLOCKSIZ){
		block = minix_bmap(inoptr, pos);
		if(block == NOBLK){
			printk("can't get NOBLOCK\n");
			*error = -EIO;
			return FALSE;
		}

		bh = bread(inoptr->i_dev, block);
		if(!bh){
			printk("error when reading block\n");
			*error = -EIO;
			return FALSE;
		}
	
		dirptr = (struct direct *)bh->b_data;
		for(i = 0; i < (BLOCKSIZ / DIRECT_SIZE); i++, dirptr++){
			if(!dirptr->d_ino)
				continue;

			if(!strncmp(name, dirptr->d_name, 30)){
				dirptr->d_ino = 0;
#if 0
				bh->b_flags |= B_ASY;
				bwrite(bh);
#else
				bh->b_flags |= B_DIRTY | B_DONE;
				brelse(bh);
#endif
				return TRUE;
			}
		}

		brelse(bh);
	}

	*error = -ENOENT;

	return FALSE;
}

static ino_t minix_findson(struct inode * inoptr, const char * name)
{
	off_t pos;
	int i, count;
	unsigned long block;
	struct direct * dirptr;
	struct buf_head * bh;
	struct minix_inode * minoptr;

	minoptr = (struct minix_inode *)inoptr->i_data;

	for(pos = 0; pos < minoptr->i_size; pos += BLOCKSIZ){
		block = minix_bmap(inoptr, pos);
		if(block == NOBLK){
			PRINTK("bmap error\n");
			return NOINO;
		}

		bh = bread(inoptr->i_dev, block);
		if(!bh){
			PRINTK("bread error\n");
			return NOINO;
		}
	
		count = BLOCKSIZ / DIRECT_SIZE;
		if(minoptr->i_size - pos < BLOCKSIZ)
			count = (minoptr->i_size - pos) / DIRECT_SIZE;

		dirptr = (struct direct *)bh->b_data;
		for(i = 0; i < count; i++, dirptr++){
			if(!dirptr->d_ino)
				continue;

			if(!strncmp(name, dirptr->d_name, 30)){
				brelse(bh);

				if(dirptr->d_ino == 1)
					return ROOT_INODE;

				return dirptr->d_ino;
			}
		}

		brelse(bh);
	}

	return NOINO;
}

static BOOLEAN minix_real_filename(
	char * page, struct inode * inoptr, int * error
	)
{
	off_t off;
	struct buf_head * bh;
	struct minix_inode * minoptr;

	minoptr = (struct minix_inode *)inoptr->i_data;

	if(minoptr->i_size >= PAGE_SIZE){
		*error = -ENAMETOOLONG;
		return FALSE;
	}

	off = 0;
	while((off < PAGE_SIZE) && (off < minoptr->i_size)){
		unsigned long block = minix_bmap(inoptr, off);

		if(block == NOBLOCK)
			memset(&page[off], 0, BLOCKSIZ);
		else{
			bh = bread(inoptr->i_dev, block);
			if(!bh){
				*error = -EIO;
				return FALSE;
			}

			memcpy(&page[off], bh->b_data, BLOCKSIZ);
			brelse(bh);
		}

		off += BLOCKSIZ;
	}

	page[minoptr->i_size] = '\0';

	return TRUE;
}

static uid_t minix_getuid(struct inode * inoptr)
{
	struct minix_inode * minoptr = (struct minix_inode *)inoptr->i_data;

	return minoptr->i_uid;
}

static void minix_setuid(struct inode * inoptr, uid_t uid)
{
	struct minix_inode * minoptr = (struct minix_inode *)inoptr->i_data;

	minoptr->i_uid = uid;
}

static gid_t minix_getgid(struct inode * inoptr)
{
	struct minix_inode * minoptr = (struct minix_inode *)inoptr->i_data;

	return minoptr->i_gid;
}

static void minix_setgid(struct inode * inoptr, gid_t gid)
{
	struct minix_inode * minoptr = (struct minix_inode *)inoptr->i_data;

	minoptr->i_gid = gid;
}

static void minix_mknod(struct inode * inoptr, dev_t dev)
{
	struct minix_inode * minoptr = (struct minix_inode *)inoptr->i_data;

	minoptr->i_zone[0] = dev;
}

static int minix_blocksize(struct inode * inoptr)
{
	return BLOCKSIZ;
}

static off_t minix_max_filesize(struct inode * inoptr)
{
	return FILESIZE_MAX;
}

static void minix_update(
	struct inode * inoptr, time_t atime, time_t mtime, time_t ctime
	)
{
	struct minix_inode * minoptr = (struct minix_inode *)inoptr->i_data;

	if(atime)
		minoptr->i_atime = atime;	

	if(mtime)
		minoptr->i_mtime = mtime;

	if(ctime)
		minoptr->i_atime = ctime;
}

static nlink_t minix_change_nlinks(struct inode * inoptr, int change)
{
	struct minix_inode * minoptr = (struct minix_inode *)inoptr->i_data;

	minoptr->i_nlinks += change;

	return minoptr->i_nlinks;
}

static int minix_get_name_of_son(
	struct inode * fino, ino_t inum, char * name, int size
	)
{
	int i;
	off_t pos;
	unsigned long block;
	struct buf_head * bh;
	struct direct * dirptr;
	struct minix_inode * mfino;

	mfino = (struct minix_inode *)fino->i_data;

	for(pos = 0; pos < mfino->i_size; pos += BLOCKSIZ){
		block = minix_bmap(fino, pos);
		if(block == NOBLK){
			printk("can't get NOBLOCK\n");
			return -EIO;
		}

		bh = bread(fino->i_dev, block);
		if(!bh){
			printk("error when reading block\n");
			return -EIO;
		}
	
		dirptr = (struct direct *)bh->b_data;
		for(i = 0; i < (BLOCKSIZ / DIRECT_SIZE); i++, dirptr++){
			if(dirptr->d_ino != inum)
				continue;

			strncpy(name, dirptr->d_name, size);

			brelse(bh);
			return 0;
		}

		brelse(bh);
	}

	return -ENOENT; // wierd, not possible
} 

static size_t get_dindir_file_zone(struct inode *inoptr)
{
	int scale;
	int ndzones;
	int nindirs;
	int size;
	struct buf_head *bh;
	struct buf_head *bh1;
	int i;
	unsigned long *pblock;
	int n;
	int count = 0;
	struct minix_inode * minoptr = (struct minix_inode *)inoptr->i_data;

	ndzones = minoptr->i_ndzones;
	nindirs = minoptr->i_nindirs;
	scale = minoptr->i_msb->s_lzsize;
	
	for (i = 0; i < ndzones + 2; i++)
	{
		if (minoptr->i_zone[i] != NOBLOCK)
		{
			count++;
		}
	}

	// get the indirect block number
	if (minoptr->i_zone[ndzones] != NOBLOCK)
	{
		bh = bread(inoptr->i_dev, minoptr->i_zone[ndzones]);
		if (!bh)
		{
			return -EIO;
		}

		pblock = (unsigned long *)&bh->b_data[0];

		for (i = 0; i < nindirs; i++)
		{
			if (pblock[i] != NOBLOCK)
			{
				count++;
			}
		}

		brelse(bh);
	}

	if (minoptr->i_zone[ndzones + 1] == NOBLOCK)
	{
		return count * (BLOCKSIZ << scale);
	}

	bh = bread(inoptr->i_dev, minoptr->i_zone[ndzones + 1]);
	if (!bh)
	{
		return -EIO;
	}

	size = minoptr->i_size;
	size -= ndzones * (BLOCKSIZ << scale);
	size -= nindirs * (BLOCKSIZ << scale);

	n = size / (nindirs * (BLOCKSIZ << scale));
	pblock = (unsigned long *)&bh->b_data[0];
	for (i = 0; i < n; i++)
	{
		int j;
		unsigned long *pblock1;
		
		if (pblock[i] == NOBLOCK)
		{
			continue;
		}

		count++;
		bh1 = bread(inoptr->i_dev, pblock[i]);
		if (!bh1)
		{
			brelse(bh);

			return -EIO;
		}

		pblock1 = (unsigned long *)&bh1->b_data[0];

		for (j = 0; j < nindirs; j++)
		{
			if (pblock1[j] != NOBLOCK)
			{
				count++;
			}
		}

		brelse(bh1);
	}

	if ((n = size % (nindirs * (BLOCKSIZ << scale))))
	{
		unsigned long *pblock1;
		
		if (pblock[i] == NOBLOCK)
		{
			brelse(bh);

			return count * (BLOCKSIZ << scale);
		}

		count++;
		bh1 = bread(inoptr->i_dev, pblock[i]);
		if (!bh1)
		{
			brelse(bh);

			return -EIO;
		}

		pblock1 = (unsigned long *)&bh1->b_data[0];
		for (i = 0; i < n / (BLOCKSIZ << scale); i++)
		{
			if (pblock1[i] != NOBLOCK)
			{
				count++;
			}
		}

		if (n % (BLOCKSIZ << scale))
		{
			if (pblock1[i] != NOBLOCK)
			{
				count++;
			}
		}

		brelse(bh1);
	}

	brelse(bh);
	
	return count * (BLOCKSIZ << scale);
}

static size_t get_file_zones(struct inode *inoptr)
{
	int n;
	int i;
	int scale;
	int ndzones;
	int nindirs;
	int size;
	int count = 0;
	struct buf_head *bh;
	struct minix_inode * minoptr = (struct minix_inode *)inoptr->i_data;

	if (inoptr->i_dev == NODEV)
	{
		return 0;
	}
	
	ndzones = minoptr->i_ndzones;
	nindirs = minoptr->i_nindirs;
	scale = minoptr->i_msb->s_lzsize;

	if (minoptr->i_size > ((ndzones + nindirs + nindirs * nindirs) * 
				(BLOCKSIZ << scale)))
	{
		return -E2BIG;
	}

	size = minoptr->i_size;
	
	if (minoptr->i_size <= (ndzones * (BLOCKSIZ << scale)))
	{
		n = minoptr->i_size / (BLOCKSIZ << scale);
		if (!(minoptr->i_size % (BLOCKSIZ << scale)))
		{
			n--;
		}
		
		for (i = 0; i <= n; i++)
		{
			if (minoptr->i_zone[i] != NOBLOCK)
			{
				count++;
			}
		}
		
		return count * (BLOCKSIZ << scale);
	}

	if (minoptr->i_size <= (ndzones + nindirs) * (BLOCKSIZ << scale))
	{
		int i;
		int size;
		int n;
		unsigned long *pblock;

		for (i = 0; i <= ndzones; i++)
		{
			if (minoptr->i_zone[i] != NOBLOCK)
			{
				count++;
			}
		}

		if (minoptr->i_zone[ndzones] == NOBLOCK)
		{
			return count * (BLOCKSIZ << scale);
		}

		bh = bread(inoptr->i_dev, minoptr->i_zone[ndzones]);
		if (!bh)
		{
			return -EIO;
		}

		size = minoptr->i_size - ndzones * (BLOCKSIZ << scale);

		n = size / (BLOCKSIZ << scale);
		if (!(size % (BLOCKSIZ << scale)))
		{
			n--;
		}

		pblock = (unsigned long *)&bh->b_data[0];

		for (i = 0; i <= n; i++)
		{
			if (pblock[i] != NOBLOCK)
			{
				count++;
			}
		}

		brelse(bh);

		return count * (BLOCKSIZ << scale);
	}

	return get_dindir_file_zone(inoptr);
}

static int minix_stat(struct inode * inoptr, struct stat * statbuf)
{
	int ret;
	struct minix_inode * minoptr = (struct minix_inode *)inoptr->i_data;

	statbuf->st_dev = inoptr->i_dev;
	statbuf->st_ino = inoptr->i_num;
	statbuf->st_mode = minoptr->i_mode;
	statbuf->st_nlink = minoptr->i_nlinks;
	statbuf->st_uid = minoptr->i_uid;
	statbuf->st_gid = minoptr->i_gid;

	if(S_ISCHR(minoptr->i_mode) || S_ISBLK(minoptr->i_mode))
		statbuf->st_rdev = (dev_t)minoptr->i_zone[0];

	statbuf->st_size = minoptr->i_size;
	statbuf->st_blksize = BLOCKSIZ;

	if ((ret = get_file_zones(inoptr)) < 0)
	{
		return ret;
	}

	statbuf->st_blocks = ret / 512;
	statbuf->st_atime = minoptr->i_atime;
	statbuf->st_atime_nsec = 0;
	statbuf->st_mtime = minoptr->i_mtime;
	statbuf->st_mtime_nsec = 0;
	statbuf->st_ctime = minoptr->i_ctime;
	statbuf->st_ctime_nsec = 0;

	return 0;
}

static int minix_readdir(int fd, struct dirent * dirp)
{
	int ret, len;
	struct direct d;

again:
	ret = do_read(fd, (char *)&d, DIRECT_SIZE);
	if(ret < 0)
		return ret;

	if(!ret)
		return 0;

	if(ret != DIRECT_SIZE){
		if(!ret) // end of dir
			return 0;

		DIE("BUG: cannot happen\n");
	}

	if(!d.d_ino)
		goto again;

	for(len = 0; len < 30; len++)
		if(!d.d_name[len])
			break;

	if(put_user4(&dirp->d_ino, (d.d_ino == 1) ? ROOT_INODE : d.d_ino) < 0)
			return -EFAULT;

	if(put_user4(&dirp->d_off, fget(fd)->f_pos) < 0)
		return -EFAULT;

	if(put_user2(&dirp->d_reclen,
		((nameoff + len + 1) + 3) & (~3)) < 0)
		return -EFAULT;

	if(memcpy_to_user(dirp->d_name, d.d_name, len) < 0)
		return -EFAULT;

	if(put_user1(&dirp->d_name[len], '\0') < 0)
		return -EFAULT;

	return DIRECT_SIZE;
}
